/*
 * ByteBufferOutputQueue.java    0.0.1    18/set/2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */ 
package org.mentalsmash.tazio.net.cs.server.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.util.LinkedList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of a chanel's output queue, using a ByteBuffer as back-end
 * <p>
 * This class and all the classes in this package are largely inspired to the presentation
 * "How To Build A Scalable NIO Server, Mark II" by Ron Hitchens (http://ronsoft.com)
 * </p>
 * @version    0.0.1    18/set/2009    
 * @author     Andrea Reale <andrea.reale@gmail.com>
 */
public class ByteBufferOutputQueue implements OutputQueue {
	/*
	 * Every method in this class is synchronised since a channel can potentially be
	 * accessed by more then one thread 
	 */
	
	private final static Logger log = LoggerFactory.getLogger(ByteBufferOutputQueue.class);
	private final BufferPool pool;
	private ChannelFacade dest; //the channel to send the output to
	private LinkedList<ByteBuffer> queue;
	private ByteBuffer current = null;

	/**
	 * Create a new ByteBufferOutputQueue with the specified values.
	 * @param pool The buffer pool from wich direct buffers will be borrowed
	 * @param dest The channel associated with this output queue
	 */
	public ByteBufferOutputQueue(BufferPool pool, ChannelFacade dest){
		if( (pool == null) || (dest == null)){
			boolean poolInvalid = (pool==null);
			String msg = poolInvalid ? "Pool is null" : "Destination is null";
			log.warn(msg);
			throw new IllegalArgumentException(msg);
		}
		
		this.pool = pool;
		this.dest = dest;
		
		queue = new LinkedList<ByteBuffer>();
	}
	
	
	/* (non-Javadoc)
	 * @see org.mentalsmash.tazio.net.server.OutputQueue#drainTo(java.nio.channels.ByteChannel)
	 */
	@Override
	public synchronized int drainTo(WritableByteChannel dest) throws IOException {
		int written = 0;
		
		while (true) {
			if (current == null) {
				if (queue.size() == 0) break; //if there's no current buffer and the queue is empty too,
				                              //there's nothing to drain

				current = queue.removeFirst();
				current.flip(); //"read-mode"
			}

			int rc = dest.write (current);
			written += rc;

			if (!current.hasRemaining()) {
				try {
					pool.returnObject (current);
				} catch (Exception e) {
					log.warn("Unable to return buffer to pool", e);
					//throw new BufferException(e);  -- maybe there's no need to throw anything here
				} finally {
					current = null;
				}
			}

			if (rc == 0) break; // if no bytes were written to the channel, that means that
			                    // the channel will no longer accept bytes
		}
		
		return written;
	}

	/* (non-Javadoc)
	 * @see org.mentalsmash.tazio.net.server.OutputQueue#enqueue(java.nio.ByteBuffer)
	 */
	@Override
	public synchronized boolean enqueue(ByteBuffer toEnqueue) {
		if(toEnqueue.remaining() == 0){
			return false; //nothing to enqueue -- is this the most correct semantics?
		}
		
		//else copy to the last buffer of the queue, if it has enough space
		if(queue.size() > 0) {
			ByteBuffer last = queue.getLast();
			
			if(last.hasRemaining()){
				fillBuffer(last,toEnqueue);
			}
		}
		
		/*
		 * If there's not enough space in the last buffer of the queue
		 * allocate a new buffer and add it to the queue.
		 */
		while (toEnqueue.hasRemaining()) { 
			
			ByteBuffer newBuf;
			try {
				newBuf = pool.borrowObject();
			} catch (Exception e) {
				log.error("Unable to borrow a buffer from the pool",e);
				throw new BufferException(e);
			}

			fillBuffer (newBuf, toEnqueue);

			queue.addLast (newBuf);
		}
		if(dest.isWritable()) {
			dest.modifyInterestOps (SelectionKey.OP_WRITE, 0); //Tell the selector there's somthing to write out
		}

		return true;
	}

	/**
	 * Fill the buffer <code>toBeFilled</code> with bytes from the buffer <code>source</code>
	 * The position of the <code>source</code> will be at the last byte read plus 1, after
	 * this method returns
	 * @param toBeFilled destination
	 * @param source source
	 */
	private void fillBuffer(ByteBuffer toBeFilled, ByteBuffer source) {
		if (source.remaining() <= toBeFilled.remaining()) {
			toBeFilled.put (source);
		} else {
			while (toBeFilled.hasRemaining()) {
				toBeFilled.put (source.get());
			}
		}
		
	}


	/* (non-Javadoc)
	 * @see org.mentalsmash.tazio.net.server.OutputQueue#isEmpty()
	 */
	@Override
	public synchronized boolean isEmpty() {
		return (current == null) && (queue.size() == 0);
	}

}
